﻿using BeerScanatronClient;
using BeerScanatronClient.Entities;
using System;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using System.Data.Objects;

namespace BeerScanatronServer
{
    [ServiceContract(Name = Constants.ServiceName)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
    internal class ScanatronService
    {
        private String _connectionString;

        public ScanatronService(string connectionString)
        {
            _connectionString = connectionString;
        }

        #region Beer

        [OperationContract]
        private void AddBeer(Beer beer)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    db.Beers.Add(beer);
                    db.SaveChanges();
                    Trace.WriteLine($"Beer {beer.Name} Added");
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private void DeleteBeers(params Beer[] beers)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    foreach (Beer beer in beers)
                    {
                        Beer tempBeer = db.Beers.Single(b => b.Id == beer.Id);
                        db.Beers.Remove(tempBeer);
                        Trace.WriteLine($"Beer {tempBeer.Name} Deleted");
                    }
                    db.SaveChanges();
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private void UpdateBeers(params Beer[] beers)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    foreach (Beer beer in beers)
                    {
                        Beer oldBeer = db.Beers.Single(b => b.Id == beer.Id);
                        oldBeer.Name = beer.Name;
                        oldBeer.Brand = beer.Brand;
                        oldBeer.Type = beer.Type;
                        Trace.WriteLine($"Beer {oldBeer.Name} updated to: {beer.Name}");
                    }
                    db.SaveChanges();
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private Beer[] GetBeers()
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                return db.Beers.ToArray();
            }
        }

        #endregion Beer

        #region Drinker

        [OperationContract]
        private void AddDrinker(Drinker drinker)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    db.Drinkers.Add(drinker);
                    db.SaveChanges();
                    Trace.WriteLine($"Drinker {drinker.FirstName} {drinker.LastName} Added");
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private void DeleteDrinkers(params Drinker[] drinkers)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    foreach (Drinker drinker in drinkers)
                    {
                        Drinker temp = db.Drinkers.Single(d => d.Id == drinker.Id);
                        db.Drinkers.Remove(temp);
                        Trace.WriteLine($"Drinker {temp.FirstName} {temp.LastName} Deleted");
                    }
                    db.SaveChanges();
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private void UpdateDrinkers(params Drinker[] drinkers)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    foreach (Drinker drinker in drinkers)
                    {
                        Drinker old = db.Drinkers.Single(d => d.Id == drinker.Id);
                        old.LastName = drinker.LastName;
                        old.FirstName = drinker.FirstName;
                        old.BarCode = drinker.BarCode;
                        old.CanServe = drinker.CanServe;
                        Trace.WriteLine($"Drinker {old.FirstName} {old.LastName} updated.");
                    }
                    db.SaveChanges();
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private Drinker[] GetDrinkers()
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                return db.Drinkers.ToArray();
            }
        }

        [OperationContract]
        private int GetDrinksServed(Drinker drinker, DateTime date)  //EntityFunctions.TruncateTime(
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                return db.DispenserInfoes
                    .Where(d => d.ServeDate.Year == date.Year
                        && d.ServeDate.Month == date.Month
                        && d.ServeDate.Day == date.Day
                        && d.DrinkerID == drinker.Id).Count();
            }
        }

        [OperationContract]
        private void AddUpdatePictures(DrinkerPicture picture)
        {
            try
            {
                using (TransactionScope tx = new TransactionScope())
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    DrinkerPicture oldPic = db.DrinkerPictures.SingleOrDefault(p => p.DrinkerID == picture.DrinkerID);
                    if (oldPic != null)
                    {
                        db.DrinkerPictures.Remove(oldPic);
                    }
                    db.SaveChanges();

                    db.DrinkerPictures.Add(picture);
                    db.SaveChanges();
                    tx.Complete();
                    Trace.WriteLine($"Drinker picture added.");
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        /// <summary>
        /// Request a single picture from the server.
        /// </summary>
        /// <param name="drinker"></param>
        /// <returns></returns>
        [OperationContract]
        private DrinkerPicture GetPicture(Drinker drinker)
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                DrinkerPicture picture = db.DrinkerPictures.Where(t => t.DrinkerID == drinker.Id).FirstOrDefault();
                return picture;
            }
        }

        #endregion Drinker

        #region Dispenser

        [OperationContract]
        private void AddDispensor(DispenserInfo dispense)
        {
            try
            {
                using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
                {
                    Beer beer = db.Beers.Single(b => b.Id == dispense.BeerID);
                    Drinker drinker = db.Drinkers.Single(d => d.Id == dispense.DrinkerID);

                    db.DispenserInfoes.Add(dispense);
                    db.SaveChanges();
                    Trace.WriteLine($"Beer {beer.ToString()} was drunk by {drinker.ToString()}!");
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw new Exception(BuildValidationError(ex));
            }
            catch (DbUpdateException ex)
            {
                throw new Exception(BuildDbUpdateException(ex));
            }
        }

        [OperationContract]
        private DispenserInfo[] GetDispensors()
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                return db.DispenserInfoes.ToArray();
            }
        }

        [OperationContract]
        private DispenserInfo[] GetDispensorsByDate(DateTime date)  //EntityFunctions.TruncateTime(
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                return db.DispenserInfoes
                    .Where(d => d.ServeDate.Year == date.Year 
                        && d.ServeDate.Month == date.Month 
                        && d.ServeDate.Day == date.Day)
                    .ToArray();
            }
        }

        [OperationContract]
        private void DeleteDispensorsByDate(DateTime date)
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                db.DispenserInfoes.RemoveRange(db.DispenserInfoes
                    .Where(d => d.ServeDate.Year == date.Year
                        && d.ServeDate.Month == date.Month
                        && d.ServeDate.Day == date.Day));
                db.SaveChanges();
            }
        }

        #endregion Dispenser

        private static string BuildValidationError(DbEntityValidationException ex)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var eve in ex.EntityValidationErrors)
            {
                sb.AppendLine($"Entity of type \"{eve.Entry.Entity.GetType().Name}\" in state \"{eve.Entry.State}\" has the following validation errors:");
                foreach (var ve in eve.ValidationErrors)
                {
                    sb.AppendLine($"- Property: \"{ve.PropertyName}\", Error: \"{ve.ErrorMessage}\"");
                }
            }
            Trace.WriteLine(sb.ToString());
            return sb.ToString();
        }

        private static string BuildDbUpdateException(DbUpdateException ex)
        {
            Exception realEx = ex;
            while (realEx.InnerException != null)
            {
                realEx = realEx.InnerException;
            }

            Trace.WriteLine(realEx.Message);
            return realEx.Message;
        }

        [OperationContract]
        private int GetMaximumBeersPerDay()
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                Setting setting = db.Settings.FirstOrDefault();
                return setting.BeerMax;
            }
        }

        [OperationContract]
        private int GetMaximumBeersPerDay_UpperLimit()
        {
            return Consts.DefaultSettings.MaximumBeersPerDayUpperLimit;
        }

        /// <summary>
        /// Set the maximum number of beers a drinker may consume in one day.
        /// </summary>
        /// <returns></returns>
        [OperationContract]
        private void SetMaximumBeersPerDay(int maximumBeers)
        {
            using (ScanatronDbContext db = new ScanatronDbContext(_connectionString))
            {
                // Update only BeerMax value without altering anything else.
                Setting setting = db.Settings.First();
                setting.BeerMax = maximumBeers;

                // Save updated data.
                db.Settings.Attach(setting);
                var entry = db.Entry(setting);
                entry.Property(e => e.BeerMax).IsModified = true;
                db.SaveChanges();
            }
        }
    }
}